package com.sisyphe.android.srouter.compiler.builder

import com.sisyphe.android.srouter.compiler.common.Constant.AUTHOR
import com.sisyphe.android.srouter.compiler.common.Constant.CLASS_FILE_WARNING
import com.sisyphe.android.srouter.compiler.common.Constant.CLASS_PROVIDER_COLLECTIONS
import com.sisyphe.android.srouter.compiler.common.Constant.PACKAGE_NAME_COLLECTIONS
import com.sisyphe.android.srouter.compiler.common.Constant.SYSTEM_TIME
import com.sisyphe.android.srouter.facade.IProvider
import com.sisyphe.android.srouter.facade.IProviderGroup
import com.sisyphe.android.srouter.facade.ProviderMeta
import com.sisyphe.android.srouter.facade.RouteItem
import com.sisyphe.android.srouter.facade.annotation.Provider
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MUTABLE_MAP
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
import com.squareup.kotlinpoet.STRING
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.buildCodeBlock
import javax.annotation.processing.Filer
import javax.annotation.processing.Messager
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.tools.Diagnostic

class ProviderBuilderImpl(
    private val filer: Filer,
    private val messager: Messager,
    private val sourceVersion: SourceVersion,
    private val moduleName: String,
    private val elementUtils: Elements,
    private val typeUtils: Types,
) : IProviderBuilder {

    private val providerSet = hashMapOf<String, RouteItem>()

    private var groupName = ""

    override fun collect(element: Element) {
        val provider = element.getAnnotation(Provider::class.java)
        val providerPath = provider.path
        val providerName = provider.name
        val providerGroup = provider.group

        if (providerPath.isNotEmpty()) {
            val pathGroup = providerPath.split("/").filter { it.isNotEmpty() }
            if (pathGroup.isNotEmpty()) {
                if (providerGroup.isNotEmpty()) {
                    this.groupName = providerGroup
                } else {
                    if (1 < pathGroup.size) {
                        this.groupName = pathGroup[0]
                    } else {
                        messager.printMessage(Diagnostic.Kind.ERROR, "The path value from Provider annotation should be '/xx/xx' or set the group value.", element)
                    }
                }
            }
        } else {
            messager.printMessage(Diagnostic.Kind.ERROR, "The path value from Provider annotation can't be empty.", element)
        }

        (element as TypeElement).interfaces.forEach {
            it.recursionFind {
                providerSet[it.toString()] = RouteItem(element = element, path = providerPath, name = providerName, group = groupName)
                return@recursionFind
            }
        }
    }

    override fun build() {
        FileSpec.builder(PACKAGE_NAME_COLLECTIONS, "$CLASS_PROVIDER_COLLECTIONS$groupName")
            .addType(
                TypeSpec.classBuilder(ClassName.bestGuess("$PACKAGE_NAME_COLLECTIONS.$CLASS_PROVIDER_COLLECTIONS${groupName}"))
                    .addSuperinterface(IProviderGroup::class)
                    .addModifiers(KModifier.FINAL)
                    .addFunction(
                        FunSpec.builder("append")
                            .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
                            .addParameter(
                                ParameterSpec.builder(
                                    "providers", MUTABLE_MAP.plusParameter(STRING).plusParameter(ProviderMeta::class.java)
                                ).build()
                            )
                            .addCode(buildCodeBlock {
                                providerSet.forEach { (key, value) ->
                                    addStatement("providers[%S] = %T(%T::class.java, %S, %S)", key, ProviderMeta::class.java, value.element, value.path, value.group)
                                }
                            })
                            .build()
                    )
                    .addKdoc(buildCodeBlock {
                        addStatement(AUTHOR)
                        addStatement("jdk version: ${sourceVersion.ordinal}")
                        addStatement(SYSTEM_TIME)
                    })
                    .build()
            )
            .addFileComment(CLASS_FILE_WARNING)
            .build()
            .writeTo(filer)
    }

    /**
     * 递归查找是否实现了 IProvider 接口
     */
    private fun TypeMirror.recursionFind(interrupt: () -> Unit) {
        val targetParent = elementUtils.getTypeElement(IProvider::class.java.canonicalName)
        val typeElement = typeUtils.asElement(this) as TypeElement
        if (targetParent == typeElement) {
            interrupt()
        } else {
            typeElement.interfaces.forEach {
                it.recursionFind(interrupt)
            }
        }
    }
}